package common

import (
	"bufio"
	"errors"
	"fmt"
	"github.com/jessevdk/go-flags"
	"os"
	"reflect"
	"regexp"
	"strings"
)

func ParseArgs(opt interface{}, args []string) (bool, error) {
	var rets []string
	var err error
	parser := flags.NewParser(opt, flags.PassDoubleDash|flags.HelpFlag)
	if args == nil {
		rets, err = parser.Parse()
	} else {
		rets, err = parser.ParseArgs(args)
	}
	if err != nil {
		if flagsErr, ok := err.(*flags.Error); !ok || flagsErr.Type != flags.ErrHelp {
			return false, errors.New("参数异常：" + err.Error())
		} else {
			helpMsg := err.Error()
			index := strings.Index(helpMsg, "Application Options")
			if index > 0 {
				helpMsg = helpMsg[index:]
			}
			fmt.Fprintln(os.Stdout, helpMsg)
			return false, nil
		}
	}
	if len(rets) != 0 {
		return false, errors.New("不支持的参数: " + strings.Join(rets, " "))
	}
	msg := _validFlags(opt)
	if msg != "" {
		return false, errors.New(msg)
	}
	return true, nil
}

func _validFlags(c interface{}) string {
	confValue := reflect.Indirect(reflect.ValueOf(c))
	confType := confValue.Type()
	fieldNum := confType.NumField()
	for i := 0; i < fieldNum; i++ {
		field := confType.Field(i)
		value := confValue.FieldByName(field.Name)
		strValue := ToString(value.Interface())
		if strValue == "" {
			continue
		}
		if _checkPattern(field, strValue) && _checkMin(field, value) && _checkMax(field, value) && _checkChoices(field, strValue) && _checkLength(field, strValue) {
			continue
		}
		errName := JoinNotBlank(", ", AddPrefix("-", field.Tag.Get("short")), AddPrefix("--", field.Tag.Get("long")))
		return fmt.Sprintf("参数错误：%s=%v%s", errName, strValue, AddPrefix("，", field.Tag.Get("description")))
	}
	return ""
}

func _checkPattern(field reflect.StructField, val string) bool {
	pattern := field.Tag.Get("regexp")
	if pattern == "" {
		return true
	}
	result, _ := regexp.MatchString(pattern, val)
	return result
}

func _checkMin(field reflect.StructField, val reflect.Value) bool {
	min := field.Tag.Get("min")
	if min == "" {
		return true
	}
	return val.Int() >= int64(ToInt(min, 0))
}

func _checkMax(field reflect.StructField, val reflect.Value) bool {
	max := field.Tag.Get("max")
	if max == "" {
		return true
	}
	return val.Int() <= int64(ToInt(max, 0))
}

func _checkChoices(field reflect.StructField, val string) bool {
	choices := field.Tag.Get("choices")
	if choices == "" {
		return true
	}
	return IndexOfStrings(ToArray(choices), val) >= 0
}

func _checkLength(field reflect.StructField, val string) bool {
	length := field.Tag.Get("length")
	if length == "" {
		return true
	}
	return len(val) <= ToInt(length, 0)
}

func SelectOption(message string, deft string, options ...string) (string, []string) {
	operations := strings.Join(options, " | ")
	reader := bufio.NewReader(os.Stdout)
	if message == "" {
		message = "请选择一个选项"
	}
	deftInfo := ""
	if deft != "" {
		deftInfo = "(默认: " + deft + ")"
	}
	for {
		fmt.Fprintf(os.Stdout, "%s [ %s ]%s: ", message, operations, deftInfo)
		values := DoScan(reader)
		if len(deft) > 0 && (len(values) == 0 || values[0] == "") {
			return deft, make([]string, 0)
		} else if IndexOfStrings(options, values[0]) >= 0 {
			if len(values) == 1 {
				return values[0], make([]string, 0)
			} else {
				return values[0], values[1:]
			}
		} else {
			fmt.Fprintf(os.Stdout, "未知的选项，请重新选择 %s\n", values[0])
		}
	}
}

func ScanConf(message string, allowEmpty bool) []string {
	reader := bufio.NewReader(os.Stdout)
	for {
		fmt.Fprintf(os.Stdout, "%s: ", message)
		values := DoScan(reader)
		if allowEmpty || len(values) > 0 && values[0] != "" {
			return values
		}
	}
}

func ScanArgs(message string, opt interface{}, required bool, tester func() string) bool {
	msg := tester()
	if msg == "" {
		return true
	}
	for {
		values := ScanConf(message, !required)
		if len(values) > 0 {
			ret, err := ParseArgs(opt, values)
			if err != nil {
				fmt.Fprintln(os.Stdout, err.Error())
			} else if ret {
				msg = tester()
				if msg == "" {
					return true
				}
				fmt.Fprintln(os.Stdout, msg)
			}
		} else if !required {
			break
		}
	}
	return false
}

func ScanMapConf(message string, allowEmpty bool) map[string]string {
	reader := bufio.NewReader(os.Stdout)
	for {
		fmt.Fprintf(os.Stdout, "%s: ", message)
		values := DoScan(reader)
		if allowEmpty || len(values) > 0 && values[0] != "" {
			return MapConf(values)
		}
	}
}

func MapConf(values []string) map[string]string {
	confMap := make(map[string]string)
	for i := 0; i < len(values)-1; i += 2 {
		confMap[values[i]] = values[i+1]
	}
	return confMap
}

func DoScan(reader *bufio.Reader) []string {
	line, err := reader.ReadString('\n')
	if err != nil {
		return make([]string, 0)
	}
	return strings.Fields(strings.TrimSpace(line))
}
